﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Telerik.Web.Mvc;
using System.ComponentModel;
using System.Collections;
using Telerik.Web.Mvc.Infrastructure;
using System.Linq.Expressions;

namespace MTO.Framework.Telerik.UI.Grid
{
    /// <summary>
    /// Sert de base pour faire du custom binding des grids telerik. Ceci permet d'exécuter le maximum de logique du côté BD.
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public abstract class GridCustomBindingBase<TEntity, TViewModel>
    {
        private bool dataSourceIsProcessed;
        private int totalCount;
        private IEnumerable<TEntity> processedDataSource;
        private IQueryable<TEntity> dataSource;
        private GridCommand command;

        public GridCustomBindingBase(GridCommand command, IQueryable<TEntity> dataSource)
        {
            this.dataSource = dataSource;
            this.command = command;
        }

        public int Total
        {
            get
            {
                EnsureDataSourceIsProcessed();
                return this.totalCount;
            }
        }

        public IEnumerable<TEntity> ProcessedDataSource
        {
            get
            {
                EnsureDataSourceIsProcessed();
                return processedDataSource;
            }
        }

        private void EnsureDataSourceIsProcessed()
        {
            if (dataSourceIsProcessed)
            {
                return;
            }

            if (this.dataSource == null)
            {
                dataSourceIsProcessed = true;
                return;
            }

            IQueryable<TEntity> data = this.dataSource;

            data = this.ApplyFiltering(data, this.command.FilterDescriptors);

            this.totalCount = data.Count();

            data = this.ApplySorting(data, command.GroupDescriptors, command.SortDescriptors);
            data = this.ApplyPaging(data, command.Page, command.PageSize);

            this.processedDataSource = data;
            this.dataSourceIsProcessed = true;
        }

        private IQueryable<TEntity> ApplyFiltering(IQueryable<TEntity> data, IList<IFilterDescriptor> filterDescriptors)
        {
            if (filterDescriptors.Any())
            {
                data = data.Where(ExpressionBuilder.Expression<TEntity>(filterDescriptors));
            }

            return data;
        }

        private IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> data, int currentPage, int pageSize)
        {
            if (pageSize > 0 && currentPage > 0)
            {
                data = data.Skip((currentPage - 1) * pageSize);
            }

            data = data.Take(pageSize);

            return data;
        }

        private IQueryable<TEntity> ApplySorting(IQueryable<TEntity> data, IList<GroupDescriptor> groupDescriptors, IList<SortDescriptor> sortDescriptors)
        {
            if (groupDescriptors.Any())
            {
                foreach (var groupDescriptor in groupDescriptors.Reverse())
                {
                    SortDescriptor sortDescriptor = new SortDescriptor
                    {
                        Member = groupDescriptor.Member,
                        SortDirection = groupDescriptor.SortDirection
                    };

                    sortDescriptors.Insert(0, sortDescriptor);
                };
            }

            if (sortDescriptors.Any())
            {
                bool isFirst = true;

                foreach (SortDescriptor sortDescriptor in sortDescriptors)
                {
                    data = AddSortExpression(data, sortDescriptor.Member, sortDescriptor.SortDirection, isFirst);
                    isFirst = false;
                }
            }

            return data;
        }

        public IEnumerable ApplyGrouping(IQueryable<TViewModel> data, IList<GroupDescriptor> groupDescriptors)
        {
            Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector = null;

            foreach (GroupDescriptor groupDescriptor in groupDescriptors.Reverse())
            {
                selector = AddGroupExpression(data, groupDescriptor, selector);
            }

            return selector.Invoke(data).ToList();
        }

        protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> BuildGroup<T>(Func<TViewModel, T> groupSelector, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selectorBuilder)
        {
            var tempSelector = selectorBuilder;

            return g => g.GroupBy(groupSelector)
                         .Select(c => new AggregateFunctionsGroup
                         {
                             Key = c.Key,
                             HasSubgroups = true,
                             Items = tempSelector.Invoke(c)
                         });
        }

        protected IEnumerable<AggregateFunctionsGroup> BuildInnerGroup<T>(IEnumerable<TViewModel> group, Func<TViewModel, T> groupSelector)
        {
            return group.GroupBy(groupSelector)
                    .Select(i => new AggregateFunctionsGroup
                    {
                        Key = i.Key,
                        Items = i.ToList()
                    });
        }

        protected Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression<T>(IQueryable<TViewModel> data, Func<TViewModel, T> property, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector)
        {
            if (selector == null)
            {
                return items => BuildInnerGroup(items, property);
            }
            else
            {
                return BuildGroup(property, selector);
            }
        }

        protected IQueryable<TEntity> AddSortExpression<T>(IQueryable<TEntity> data, Expression<Func<TEntity, T>> property, ListSortDirection sortDirection, bool isFirst)
        {
            if (sortDirection == ListSortDirection.Ascending)
            {
                return isFirst ? data.OrderBy(property) : ((IOrderedQueryable<TEntity>)data).ThenBy(property);
            }

            return isFirst ? data.OrderByDescending(property) : ((IOrderedQueryable<TEntity>)data).ThenByDescending(property);
        }

        protected abstract Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> AddGroupExpression(IQueryable<TViewModel> data, GroupDescriptor groupDescriptor, Func<IEnumerable<TViewModel>, IEnumerable<AggregateFunctionsGroup>> selector);

        protected abstract IQueryable<TEntity> AddSortExpression(IQueryable<TEntity> data, string memberName, System.ComponentModel.ListSortDirection sortDirection, bool isFirst);
    }
}
